// Parser.cpp : Defines the entry point for the console application and read source code from file.
//				Parse SIMPLE source code into AST like structure.
//				Insert into relevant VarTable, ModifyUsesTable, ProcTable, CallsTable.
// Author: Heng Belson
//
#include "Parser.h"
#include "VarTable.h"
#include "ProcTable.h"
#include <fstream>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>

using namespace boost;

#pragma region Token Declarations
/******************************/
/* List of tokens/ keywords	   /
/******************************/
TOKEN T_PROCEDURE = "procedure";
TOKEN T_CALL = "call";
TOKEN T_WHILE = "while";
TOKEN T_IF = "if";
TOKEN T_THEN = "then";
TOKEN T_ELSE = "else";
TOKEN T_TIMES = "*";
TOKEN T_PLUS = "+";
TOKEN T_MINUS = "-";
TOKEN T_EQUAL = "=";
TOKEN T_OPENBRACE = "{";
TOKEN T_CLOSEBRACE = "}";
TOKEN T_SEMICOLON = ";";
//TOKEN T_LPAREN = "(";
//TOKEN T_RPAREN =")";
TOKEN T_WHITESPACE = " ";
/******************************/
#pragma endregion

#pragma region Constructor
Parser::Parser(AST *ast) {
	stream = "";
	fileLocation = "";
	offset = 0;
	builder = AST_Builder(ast);
}
#pragma endregion

#pragma region Main Functions Implementation
/******************************/
/* Function Name: Parse
/* Description	: 
/******************************/
STATUS Parser::Parse(STRING readFromFile) {
	fileLocation = readFromFile;
	/***************************/
	/* Set Token Conditions     /
	/***************************/
	TOKEN_LIST token;
	token.push_back(T_PROCEDURE);
	/***************************/

	Match(token);
	Program();
	PopulateTables();

	//////////////////////////////** Do AST */
	for (INTEGER i = 0; i < (INTEGER) program.procedures.size(); i++) {
		ProcTable *proc_table = ProcTable::getInstance();
		PROC_INDEX procedure_id = proc_table->getProcIndex(program.procedures[i].name);

		builder.addProcedure(procedure_id);
		doStatement(program.procedures[i].statementList);
	}
	//////////////////////////////////////////
	return STATUS_Success;
}

/******************************/
/* Function Name: Program
/* Description	: 
/******************************/
#pragma region Program
void Parser::Program() {
	/***************************/
	/* Set Token Conditions     /
	/***************************/
	TOKEN_LIST token;
	token.push_back(T_OPENBRACE);
	/***************************/	

	Match(token);

	erase_all(stream, T_OPENBRACE); trim(stream);

	program.name = stream;
	Procedure(program.name);
}
#pragma endregion

/******************************/
/* Function Name: Procedure
/* Description	: 
/******************************/
#pragma region Procedure
void Parser::Procedure(STRING procedureName) {
	ST_StatementList stmtLst;
	procedure.name = procedureName;
	p.push(procedureName);

	stmtLst = StatementList(stmtLst);
	procedure.statementList = stmtLst;
	program.procedures.push_back(procedure);

	/*****************************/
	/* Reset Procedure Container  /
	/*****************************/
	procedure.name.clear();
	procedure.statementList = ST_StatementList();
	/*****************************/

	if (offset != -1) {		// Not End of File
		/***************************/
		/* Set Token Conditions     /
		/***************************/
		TOKEN_LIST token;
		token.push_back(T_PROCEDURE); Match(token); 
		token.clear(); 
		token.push_back(T_OPENBRACE); Match(token); 
		/******************************************/

		erase_all(stream, T_OPENBRACE); trim(stream);
		Procedure(stream); 
	}
}
#pragma endregion

/******************************/
/* Function Name: StatementList
/* Description	: 
/******************************/
#pragma region Statement List
Parser::ST_StatementList Parser::StatementList(ST_StatementList stmtLst) {
	/***************************/
	/* Set Token Conditions     /
	/***************************/
	TOKEN_LIST token;
	token.push_back(T_SEMICOLON);
	token.push_back(T_IF);
	token.push_back(T_WHILE);
	token.push_back(T_CALL);
	/***************************/

	Match(token);

	while (!isFound(T_CLOSEBRACE, stream)) {
		stmtLst = Statement(stmtLst);
		Match(token);
	}
	return stmtLst;
}
#pragma endregion

/******************************/
/* Function Name: Statement
/* Description	: 
/******************************/
#pragma region Statement
Parser::ST_StatementList Parser::Statement(ST_StatementList stmtLst) {
	ST_Statement stmt; 
	TOKEN_LIST token;
	COLLECTION compoundExpr;

	// Case 1: While Statement
	if (isFound(T_WHILE, stream)) {
		ST_While whileStat;
		ST_StatementList body;

		token.push_back(T_OPENBRACE);
		Match(token);

		compoundExpr = splitAt(stream, T_WHITESPACE);
		whileStat.condition = compoundExpr[0];

		//////////////////////////////////////////////
		v.push(whileStat.condition);
		/////////////////////////////////////////////

		body = StatementList(body);
		whileStat.body = body;

		stmt.whileStat = whileStat;
		stmt.statementType = STATEMENT_While;

		stmtLst.statement.push_back(stmt);
	}
	// Case 2: If Statement
	else if (isFound(T_IF, stream)) {
		ST_If ifStat;
		ST_StatementList thenPart, elsePart;

		token.push_back(T_THEN);
		Match(token);

		compoundExpr = splitAt(stream, T_WHITESPACE);
		ifStat.condition = compoundExpr[0];
		
		//////////////////////////////////////////////
		v.push(ifStat.condition);
		/////////////////////////////////////////////

		token.clear();

		token.push_back(T_OPENBRACE);
		Match(token);

		thenPart = StatementList(thenPart);

		Match(token);

		elsePart = StatementList(elsePart);

		ifStat.thenPart = thenPart;
		ifStat.elsePart = elsePart;
		
		stmt.ifStat = ifStat;
		stmt.statementType = STATEMENT_If;

		stmtLst.statement.push_back(stmt);
	}
	// Case 3: Assignment Statement
	else if (isFound(T_EQUAL, stream)) {
		ST_Assignment assignStat; 
		ST_Expression exprStat;
		EXPRESSION_LIST expression;
		STRING RHS;
		
		compoundExpr = splitAt(stream, T_EQUAL);
		assignStat.leftVar = compoundExpr[0];

		//////////////////////////////////////////////
		v.push(assignStat.leftVar);
		/////////////////////////////////////////////

		expression.push_back(exprStat);
		RHS = compoundExpr[1];

		assignStat.expression = EvaluateExpression(RHS, expression);

		stmt.assignStat = assignStat;
		stmt.statementType = STATEMENT_Assign;

		stmtLst.statement.push_back(stmt);
	}
	// Case 4: Call Statement
	else if (isFound(T_CALL, stream)) {
		ST_Call callStat;

		token.push_back(T_SEMICOLON);
		Match(token);
		
		erase_all(stream, T_SEMICOLON);
		trim(stream);
		callStat.procedureName = stream;
		
		stmt.callStat = callStat;
		stmt.statementType = STATEMENT_Call;
		
		stmtLst.statement.push_back(stmt);
	}
	return stmtLst;
}
#pragma endregion
#pragma endregion

#pragma region Sub Functions Implementation
#pragma region Binary Operation
Parser::EXPRESSION_LIST Parser::BinaryOperation(STRING expression, EXPRESSION_LIST expr) {
	COLLECTION operands;
	STRING LHS, RHS, OP;

	// Case 1: TIMES operation
	if (isFound(T_TIMES, expression)) { 
		ST_Times mul;
		operands = getOperands(expression, T_TIMES);
		
		LHS = operands[0]; RHS = operands[1];

		mul.rightBranch = RHS;
		
		//////////////////////////////////////////////
		v.push(mul.rightBranch);
		/////////////////////////////////////////////

		if (isReducible(LHS)) { mul.leftBranch = BinaryOperation(LHS, expr); }
		else mul.leftBranch = EvaluateExpression(LHS, expr);

		expr.back().multiply = mul;
		expr.back().exprType = EXPRESSION_Times;
	}
	// Case 2: PLUS operation
	if (isFound(T_PLUS, expression)) {
		if (isFound(T_MINUS,expression)) { 
			INTEGER plusPos = (INTEGER)expression.find_last_of(T_PLUS);
			INTEGER minusPos = (INTEGER)expression.find_last_of(T_MINUS);

			if (plusPos > minusPos) { OP = T_PLUS; }
			else { OP = T_MINUS; }
		}
		ST_Plus plus;
		if (OP.empty()) OP = T_PLUS;
		operands = getOperands(expression, OP);

		LHS = operands[0]; RHS = operands[1];
		
		plus.rightBranch = RHS;

		//////////////////////////////////////////////
		v.push(plus.rightBranch);
		/////////////////////////////////////////////

		if (isReducible(LHS)) { plus.leftBranch = BinaryOperation(LHS, expr); }
		else plus.leftBranch = EvaluateExpression(LHS, expr);

		expr.back().plus = plus;
		expr.back().exprType = EXPRESSION_Plus;
	}
	// Case 3: MINUS operation
	if (isFound(T_MINUS, expression)) {
		if (isFound(T_PLUS,expression)) { 
			INTEGER plusPos = (INTEGER)expression.find_last_of(T_PLUS);
			INTEGER minusPos = (INTEGER)expression.find_last_of(T_MINUS);

			if (plusPos > minusPos) { OP = T_PLUS; }
			else { OP = T_MINUS; }
		}
		ST_Minus minus;
		if (OP.empty()) OP = T_MINUS;
		operands = getOperands(expression, OP);

		LHS = operands[0]; RHS = operands[1];

		minus.rightBranch = RHS;

		//////////////////////////////////////////////
		v.push(minus.rightBranch);
		/////////////////////////////////////////////

		if (isReducible(LHS)) { minus.leftBranch = BinaryOperation(LHS, expr); }
		else minus.leftBranch = EvaluateExpression(LHS, expr);

		expr.back().minus = minus;
		expr.back().exprType = EXPRESSION_Minus;
	}
	return expr;
}
#pragma endregion

#pragma region Evaluate Expression
Parser::EXPRESSION_LIST Parser::EvaluateExpression(STRING eval, EXPRESSION_LIST exprLst) {
	// Case 1: Binary Operation 
	if (isReducible(eval)) { exprLst = BinaryOperation(eval, exprLst); }

	// Case 2: Constant
	else if (isConstant(eval)) {
		ST_Constant constant;
		constant.value = lexical_cast<INTEGER>(eval);

		v.push(eval);

		exprLst.back().constant = constant;
		exprLst.back().exprType = EXPRESSION_Constant;
	}

	// Case 3: Variable
	else {
		ST_Variable var;
		var.name = eval;

		v.push(eval);

		exprLst.back().variable = var;
		exprLst.back().exprType = EXPRESSION_Variable;
	}
	return exprLst;
}
#pragma endregion

#pragma region Populate Tables
void Parser::PopulateTables() {
	/** Insert Procedures into ProcTable */
	while ((INTEGER) p.size() > 0) {
		ProcTable *proc_table = ProcTable::getInstance();
		proc_table->insertProc(p.front());
		p.pop();
	}

	/** Insert Variables, Constants into VarTable */
	while ((INTEGER) v.size() > 0) {
		VarTable *var_table = VarTable::getInstance();
		var_table->insertVar(v.front(), (isConstant(v.front())) ? NUMERIC_EXPR_TYPE_CONSTANT : NUMERIC_EXPR_TYPE_VARIABLE);
		v.pop();
	}
}
#pragma endregion

#pragma region Match
void Parser::Match(TOKEN_LIST token) {
	stream.clear();
	Scanner(token);
}
#pragma endregion

#pragma region Scanner
void Parser::Scanner(TOKEN_LIST token) {
	STRING character;
	ifstream file;

	try { file.open(fileLocation); } catch (char &ex) { cerr << "Exception raised: " << ex << endl; system("PAUSE"); exit(1); }
	
	try {
		file.seekg(offset);

		if (file.is_open()) {
			while (file.good()) {
				character = file.get();
				if (character.compare(T_CLOSEBRACE) == 0) { token.clear(); token.push_back(T_CLOSEBRACE); }
				if (isFound(token, stream)) {
					offset = (INTEGER) file.tellg();
					stream.append(character);
					break;
				}
				else { stream.append(character); }
			}
		}
		else { cerr << "Error accessing the file: " << fileLocation << endl; system("PAUSE"); exit(1); }
	} catch (char &ex) { cerr << "Exception raised: " << ex << endl; system("PAUSE"); exit(1); }
	
	file.close();
}
#pragma endregion
#pragma endregion

#pragma region Support Functions Implementation
#pragma region Get Operands
COLLECTION Parser::getOperands(STRING expression, STRING op) {
	COLLECTION operands;
	INTEGER position;
	
	position = (INTEGER)expression.find_last_of(op);
	if (position > -1) {
		STRING tmp = expression.substr(0, position);
		trim(tmp);
		operands.push_back(tmp);
	}
	STRING tmp = expression.substr(position+1, expression.length()-1);
	trim(tmp);
	operands.push_back(tmp);
	return operands;
}
#pragma endregion

#pragma region Do Statement
STRING Parser::doStatement(ST_StatementList stmtLst) {
	INTEGER i = 0;
	INTEGER numberOfStatement = stmtLst.statement.size();

	STRING printStmt;

	while (i < numberOfStatement) {
		STATEMENT_TYPE stmtType = stmtLst.statement.at(i).statementType;

		switch (stmtType) {
		case STATEMENT_Assign: 
			{
				VarTable *var_table = VarTable::getInstance();
				VAR_INDEX left_variable_id = var_table->getVarIndex(stmtLst.statement[i].assignStat.leftVar);

				builder.addAssign(left_variable_id);
				doExpression(stmtLst.statement[i].assignStat.expression, true);

				//printStmt.operator+=("Assignment Statement: \n");
				//printStmt.operator+=("Left Var: ");
				//printStmt.operator+=(stmtLst.statement.at(i).assignStat.leftVar);
				//printStmt.operator+=("\n");
				//printStmt.operator+=("Expression: ");
				//printStmt.operator+=(doExpression(stmtLst.statement.at(i).assignStat.expression));
				//printStmt.operator+=("\n");
				break;
			}
		case STATEMENT_Call: 
			{
				ProcTable *proc_table = ProcTable::getInstance();
				PROC_INDEX call_id = proc_table->getProcIndex(stmtLst.statement[i].callStat.procedureName);

				builder.addCall(call_id);

				//printStmt.operator+=("Call Statement: ");
				//printStmt.operator+=(stmtLst.statement.at(i).callStat.procedureName);
				//printStmt.operator+=("\n");
				break;
			}
		case STATEMENT_While: 
			{
				VarTable *var_table = VarTable::getInstance();
				VAR_INDEX condition_id = var_table->getVarIndex(stmtLst.statement[i].whileStat.condition);

				builder.beginWhile(condition_id);
				doStatement(stmtLst.statement[i].whileStat.body);
				builder.endWhile();

				//printStmt.operator+=("While Statement: \n");
				//printStmt.operator+=("Condition: ");
				//printStmt.operator+=(stmtLst.statement.at(i).whileStat.condition);
				//
				//printStmt.operator+=("\n");
				//printStmt.operator+=("Body: \n");
				//printStmt.operator+=(doStatement(stmtLst.statement.at(i).whileStat.body));
				//printStmt.operator+=("\n");
				break;
			}
		case STATEMENT_If:
			{
				VarTable *var_table = VarTable::getInstance();
				VAR_INDEX condition_id = var_table->getVarIndex(stmtLst.statement[i].ifStat.condition);

				builder.beginIf(condition_id);
				doStatement(stmtLst.statement[i].ifStat.thenPart);
				builder.endIf(if_stmtNode::THEN_BLOCK);
				doStatement(stmtLst.statement[i].ifStat.elsePart);
				builder.endIf(if_stmtNode::ELSE_BLOCK);

				//printStmt.operator+=("If Statement: \n");
				//printStmt.operator+=("Condition: "); 
				//printStmt.operator+=(stmtLst.statement.at(i).ifStat.condition);
				//printStmt.operator+=("\n");
				//printStmt.operator+=("Then Part: "); 
				//printStmt.operator+=(doStatement(stmtLst.statement.at(i).ifStat.thenPart));
				//printStmt.operator+=("\n");
				//printStmt.operator+=("Else Part: ");
				//printStmt.operator+=(doStatement(stmtLst.statement.at(i).ifStat.elsePart));
				//printStmt.operator+=("\n");
				break;
			}
		default:
			cerr << "Unknown STATEMENT_TYPE: " << stmtType;
			break;
		}
		i++;
	}	
	return printStmt;
}
#pragma endregion

#pragma region Do Expression
STRING Parser::doExpression(EXPRESSION_LIST expression, BOOLEAN isReducedExpression) {
	STRING printStmt;
	VarTable *var_table = VarTable::getInstance();
	VAR_INDEX id;
	EXPRESSION_TYPE exprType = expression[0].exprType;

	switch (exprType) {
	case EXPRESSION_Times:
		{
			builder.beginExpr(OP_Times);
			doExpression(expression[0].multiply.leftBranch, false);

			id = var_table->getVarIndex(expression[0].multiply.rightBranch);

			builder.setRightBranch(NUMERIC_EXPR_TYPE_VARIABLE, id);
			builder.endExpr(composite_expr::ADD_TO_LHS);

	/*		printStmt.operator+=("Times: \n");
			printStmt.operator+=("Left Branch: ");
			printStmt.operator+=(doExpression(expression.at(0).multiply.leftBranch));
			printStmt.operator+=("\n");
			printStmt.operator+=("Right Branch: ");
			printStmt.operator+=(expression.at(0).multiply.rightBranch);
			printStmt.operator+=("\n");		*/	
			break;
		}
	case EXPRESSION_Plus:
		{
			builder.beginExpr(OP_Plus);
			doExpression(expression[0].plus.leftBranch, false);

			id = var_table->getVarIndex(expression[0].plus.rightBranch);

			builder.setRightBranch(NUMERIC_EXPR_TYPE_VARIABLE, id);
			builder.endExpr(composite_expr::ADD_TO_LHS);

			//printStmt.operator+=("Plus: \n");
			//printStmt.operator+=("Left Branch: ");
			//printStmt.operator+=(doExpression(expression.at(0).plus.leftBranch));
			//printStmt.operator+=("\n");
			//printStmt.operator+=("Right Branch: ");
			//printStmt.operator+=(expression.at(0).plus.rightBranch);
			//printStmt.operator+=("\n");
			break;
		}
	case EXPRESSION_Minus:
		{
			builder.beginExpr(OP_Minus);
			doExpression(expression[0].minus.leftBranch, false);

			id = var_table->getVarIndex(expression[0].minus.rightBranch);

			builder.setRightBranch(NUMERIC_EXPR_TYPE_VARIABLE, id);
			builder.endExpr(composite_expr::ADD_TO_LHS);

	/*		printStmt.operator+=("Minus: \n");
			printStmt.operator+=("Left Branch: ");
			printStmt.operator+=(doExpression(expression.at(0).minus.leftBranch));
			printStmt.operator+=("\n");
			printStmt.operator+=("Right Branch: ");
			printStmt.operator+=(expression.at(0).minus.rightBranch);
			printStmt.operator+=("\n");	*/		
			break;
		}
	case EXPRESSION_Constant:
		{
			VAR constant = lexical_cast<STRING>(expression[0].constant.value);
			id = var_table->getVarIndex(constant);

			if (isReducedExpression) {
				builder.addNumericExpr(NUMERIC_EXPR_TYPE_CONSTANT, id);
			} else {
				builder.setLeftBranch(NUMERIC_EXPR_TYPE_CONSTANT, id);
			}
			//printStmt.operator+=("Constant: \n");
			//printStmt.operator+=(expression.at(0).constant.value);
			break;
		}
	case EXPRESSION_Variable:
		{
			id = var_table->getVarIndex(expression[0].variable.name);

			if (isReducedExpression) {
				builder.addNumericExpr(NUMERIC_EXPR_TYPE_VARIABLE, id);
			} else {
				builder.setLeftBranch(NUMERIC_EXPR_TYPE_VARIABLE, id);
			}
			//printStmt.operator+=("Variable: \n");
			//printStmt.operator+=(expression.at(0).variable.name);
			break;
		}
	default:
		cerr << "Unknown EXPRESSION_TYPE: " << exprType;
		break;
	}
	return printStmt;
}
#pragma endregion

#pragma region Is Constant
BOOLEAN Parser::isConstant(STRING expression) {
	for (INTEGER i = 0; i < (INTEGER) expression.length(); i++) {
		if (!isdigit(expression[i])) return false;
	}
	return true;
}
#pragma endregion

#pragma region Is Reducible
BOOLEAN Parser::isReducible(STRING expression) {
	return (isFound(T_TIMES, expression) || isFound(T_PLUS, expression) || isFound(T_MINUS, expression));
}
#pragma endregion

#pragma region Is Found
BOOLEAN Parser::isFound(TOKEN_LIST token, STRING line) {
	INTEGER i = 0;
	for (; i < (INTEGER)token.size(); i++) {
		size_t found = line.find(token.at(i));
		if (found != STRING::npos) return true; 
	}
	return false;
}

BOOLEAN Parser::isFound(TOKEN token, STRING line) {
	size_t found = line.find(token);
	if (found != STRING::npos) return true; else return false;
}
#pragma endregion

#pragma region Split At
COLLECTION Parser::splitAt(STRING word, STRING delimiter) {
	COLLECTION tokenizer;
	erase_all(word, T_SEMICOLON);
	split(tokenizer, word, is_any_of(delimiter));
	
	for (INTEGER i = 0; i < (INTEGER) tokenizer.size(); i++) {
		trim(tokenizer[i]);
	}
	return tokenizer;
}
#pragma endregion
#pragma endregion